using System;
using System.Collections.Generic;
using Blackbird.Core.Entities.Sprites;
using Blackbird.Core.Interface;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Blackbird.Core.Base {
    public class BaseRenderable : IComparable<BaseRenderable>, IRenderable {
        #region Events

        public event ScreenRenderableClickEvent Click;
        public event ScreenRenderableCollisionEvent Collide;
        public event ScreenUpdateEvent BeforeUpdate;
        public event ScreenUpdateEvent AfterUpdate;
        public event ScreenRenderEvent BeforeRendering;
        public event ScreenRenderEvent AfterRendering;

        #endregion

        #region State Properties

        private Vector2 _relativeMousePosition;
        public string Id { get; set; }
        public object Tag { get; set; }
        public virtual bool Updated { get; set; }

        public Vector2 RelativeMousePosition {
            get { return _relativeMousePosition; }
        }

        #endregion

        #region Geometry Properties

        private Vector2 _center;
        private Vector2 _transformedPosition;

        public virtual Vector2 Center
        {
            get { return _center; }
            protected set
            {
                _center = value;
                CalcRect();
            }
        }

        protected Vector2 Position { get; set; }
        public virtual Vector2 Origin { get; set; }
        public virtual float LayerDepth { get; set; }
        public virtual Vector2 Velocity { get; set; }
        public virtual Rectangle Rectangle { get; set; }

        #endregion

        #region Rendering Properties

        public virtual Frame CurrentFrame { get; set; }

        public virtual Color ModulationColor { get; set; }
        public virtual SpriteEffects Effects { get; set; }
        public virtual Vector2 Scale { get; set; }
        public virtual float Rotation { get; set; }
        public virtual bool Visible { get; set; }

        #endregion

        #region Behavior Properties

        private bool _mouseOver;
        protected float OrbitDelta;
        protected Vector2 OrbitOffset;

        private bool _orbiting;
        protected int CollisionThreashold { get; set; }

        public virtual bool Orbiting {
            get { return _orbiting; }
            set {
                _orbiting = value;
                if (_orbiting) {
                    OrbitOffset = Position;
                } else {
                    Position = OrbitOffset;
                }
            }
        }

        public byte[] CollisionData { get; set; }
        public virtual float OrbitRadius { get; set; }
        public virtual float OrbitVelocity { get; set; }
        public virtual BaseRenderable Parent { get; set; }
        public virtual BaseRenderable Prey { get; set; }
        public virtual bool Rotating { get; set; }
        public virtual float RotationSpeed { get; set; }
        public virtual float ChaseSpeed { get; set; }

        protected bool MouseOver {
            get { return _mouseOver; }
        }

        #endregion

        #region IRenderable implementation

        public virtual void Initialize()
        {
            Scale = new Vector2(1.0f);
            Origin = new Vector2(0.0f, 0.0f);
            Visible = true;
            ModulationColor = Color.White;
        }

        public virtual void Render(SpriteBatch spriteBatch) {
            if (Visible) {
                if (BeforeRendering != null) {
                    var e = new ScreenRenderEventArgs(spriteBatch);
                    BeforeRendering(this, e);
                }

                var destination = new Rectangle((int)_transformedPosition.X, (int)_transformedPosition.Y,
                                                (int)(TexturePool.Instance[CurrentFrame.TextureId].Width * Scale.X),
                                                (int)(TexturePool.Instance[CurrentFrame.TextureId].Height * Scale.Y));

                spriteBatch.Draw(TexturePool.Instance[CurrentFrame.TextureId], destination, null, ModulationColor, Rotation,
                                 Origin, Effects,
                                 LayerDepth);
                if (AfterRendering != null) {
                    var e = new ScreenRenderEventArgs(spriteBatch);
                    BeforeRendering(this, e);
                }
            }
            Updated = false;
        }

        public virtual void Update(long delta) {
            if (Updated)
                return;
            Updated = true;

            if (BeforeUpdate != null) {
                var e = new ScreenUpdateEventArgs(delta);
                BeforeUpdate(this, e);
            }

            float factor = ((float)delta / 1000000);
            Vector2 deltaVector;

            //Handle orbit
            if ((Orbiting) && Parent != null) {
                Parent.Update(delta);
                OrbitDelta += (OrbitVelocity * factor);
                if (OrbitDelta > 360.0f)
                    OrbitDelta %= 360;
                double radians = OrbitDelta / 57.29577951f;
                deltaVector = new Vector2((float)Math.Sin(radians) * OrbitRadius,
                                          (float)Math.Cos(radians) * OrbitRadius);
                Center = Parent.Center + deltaVector;
                _transformedPosition = Position;
            } else if (Prey != null) {
                //Handle the chasing logic.
                float length = Velocity.Length();
                Velocity = GetNormalizedVectorBetween(this, Prey);
                Velocity *= length;
                Position += (Velocity * factor);
                _transformedPosition = Position;
            } else {
                //Handle the normal movement logic.
                deltaVector = Velocity * factor;

                Position += deltaVector;
                if (Parent != null) {
                    Parent.Update(delta);
                    _transformedPosition = Parent.Position + Position;
                } else
                    _transformedPosition = Position;
            }

            if (Rotating)
                Rotation += (RotationSpeed * factor);

            if (AfterUpdate != null) {
                var e = new ScreenUpdateEventArgs(delta);
                AfterUpdate(this, e);
            }
        }

        #endregion

        #region IComparable<BaseRenderable> Members

        public int CompareTo(BaseRenderable other) {
            return LayerDepth.CompareTo(other.LayerDepth);
        }

        #endregion

        /// <summary>
        /// Changes the direction of the object so as to move towards the provided 
        /// target, and maintans the overall velocity.
        /// </summary>
        /// <param name="target"></param>
        public void GoTowards(BaseRenderable target) {
            float length = Velocity.Length();
            Velocity = GetNormalizedVectorBetween(this, Prey);
            Velocity *= length;
        }

        /// <summary>
        /// Changes the direction of the object so as to move towards the provided 
        /// target, and maintans the overall velocity.
        /// </summary>
        /// <param name="targetPoint"></param>
        public void GoTowards(Vector2 targetPoint) {
            float length = Velocity.Length();
            Velocity = GetNormalizedVectorBetween(this, targetPoint);
            Velocity *= length;
        }

        /// <summary>
        /// Adjusts the velocity by the given amount.
        /// </summary>
        /// <param name="amount">The amount to adjust by.</param>
        public void AdjustSpeedBy(float amount) {
            float length = Velocity.Length() + amount;
            Velocity.Normalize();
            Velocity *= length;
        }

        /// <summary>
        /// Sets the speed and maintains the direction.
        /// </summary>
        /// <param name="speed"></param>
        public void SetSpeed(float speed) {
            Velocity.Normalize();
            Velocity *= speed;
        }

        /// <summary>
        /// Sets the direction and maintains the speed.
        /// </summary>
        /// <param name="direction"></param>
        public void SetDirection(Vector2 direction) {
            direction.Normalize();
            float speed = Velocity.Length();
            Velocity = direction * speed;
        }

        protected virtual void CalcCenter() {
            float x = Position.X + (Rectangle.Width / 2.0f);
            float y = Position.Y + (Rectangle.Height / 2.0f);
            _center = new Vector2(x, y);
        }

        protected virtual void CalcRect() {
            Rectangle = new Rectangle((int)Position.X, (int)Position.Y, TexturePool.Instance[CurrentFrame.TextureId].Width,
                                      TexturePool.Instance[CurrentFrame.TextureId].Height);
        }

        public void DoClick(MouseState mouseState, List<Keys> keys) {
            if (Click != null) {
                var relativeMousePosition = new Vector2(mouseState.X - Position.X, mouseState.Y - Position.Y);
                var e = new ScreenRenderableClickEventArgs(mouseState, keys, relativeMousePosition);
                Click(this, e);
            }
        }

        public void DoCollide(BaseRenderable collider) {
            if (Collide != null) {
                var e = new ScreenRenderableCollisionEventArgs(collider);
                Collide(this, e);
            }
        }

        public void SetMouseOver(bool value) {
            _mouseOver = value;
        }

        public void CalculateMousePosition(int x, int y) {
            _relativeMousePosition = new Vector2(_transformedPosition.X - x, _transformedPosition.Y - y);
        }

        public bool PixelCollide(BaseRenderable target) {
            // Find the bounds of the rectangle intersection
            int top = Math.Max(Rectangle.Top, target.Rectangle.Top);
            int bottom = Math.Min(Rectangle.Bottom, target.Rectangle.Bottom);
            int left = Math.Max(Rectangle.Left, target.Rectangle.Left);
            int right = Math.Min(Rectangle.Right, target.Rectangle.Right);

            // Check every point within the intersection bounds
            for (int y = top; y < bottom; y++)
                for (int x = left; x < right; x++) {
                    byte byteA = CollisionData[(x - Rectangle.Left) + (y - Rectangle.Top) * Rectangle.Width];
                    byte byteB =
                        target.CollisionData[
                            (x - target.Rectangle.Left) + (y - target.Rectangle.Top) * target.Rectangle.Width];
                    if (byteA + byteB > CollisionThreashold)
                        return true;
                }

            return false;
        }

        public static Vector2 GetNormalizedVectorBetween(BaseRenderable r1, BaseRenderable r2) {
            Vector2 vector = r2.Center - r1.Center;
            vector.Normalize();
            return vector;
        }

        public static Vector2 GetNormalizedVectorBetween(BaseRenderable r1, Vector2 targetPoint) {
            Vector2 vector = targetPoint - r1.Center;
            vector.Normalize();
            return vector;
        }

        public static Vector2 GetNormalizedVectorBetween(Vector2 originalPoint, Vector2 targetPoint) {
            Vector2 vector = targetPoint - originalPoint;
            vector.Normalize();
            return vector;
        }

        public static float CalcDistance(BaseRenderable r1, BaseRenderable r2) {
            double x = (float)Math.Pow((r1.Center.X - r2.Center.X), 2.0);
            double y = (float)Math.Pow((r1.Center.Y - r2.Center.Y), 2.0);

            return (float)Math.Sqrt(x + y);
        }
    }
}